using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics;

namespace TelnetExpect
{
    /// <summary>
    /// Allows the peer to enable <see cref="F:TelnetExpect.TelnetOption.TerminalType"/>
    /// and <see cref="TelnetExpect.TelnetOption.NegotiateAboutWindowSize"/> by negotiation.
    /// Responds to TerminalType requests with &#x201C;ANSI&#x201D;, and to NAWS
    /// with result from overridden method <see cref="M:TelnetExpect.TerminalAnsiTelnetHandlerBase.GetWindowSize(System.Int32@,System.Int32@)"/>.
    /// </summary>
    public abstract class TerminalAnsiTelnetHandlerBase : TelnetOptionHandler
    {
        /// <summary>
        /// Initializes an instance of class <see cref="T:TerminalAnsiTelnetHandlerBase"/>.
        /// </summary>
        protected TerminalAnsiTelnetHandlerBase()
        {
        }

        /// <exclude/>
        protected internal override void Init()
        {
            base.Init();
            Telnet.SetRemoteMode(TelnetOption.Echo, true);
            Telnet.SetRemoteMode(TelnetOption.SuppressGoAhead, true);
            //
            Telnet.SetLocalMode(TelnetOption.TerminalType, true);
            Telnet.SetLocalMode(TelnetOption.NegotiateAboutWindowSize, true);
            //try {
            //    Console.CursorLeft = Console.CursorLeft;
            //    //
            //    // Now we've tested we're not redirected to a file etc!
            //    Telnet.SetLocalMode(TelnetOption.TerminalType, true);
            //    Telnet.SetLocalMode(TelnetOption.NegotiateAboutWindowSize, true);
            //} catch (IOException) {
            //    TraceWriteLine("Disabling Telnet TerminalType (ANSI) as not on a Console.");
            //}
        }


        /// <exclude/>
        protected override void HandleSubNegotiation(byte[] data)
        {
            Debug.Assert(data != null, "ArgEx data!=null");
            Debug.Assert(data.Length != 0, "ArgEx data.Length==0");
            byte optionNumber = data[0];
            TelnetOption option = (TelnetOption)optionNumber;
            if (option == TelnetOption.TerminalType) {
                Debug.Assert(Telnet.GetLocalMode(TelnetOption.TerminalType), "NOT Option Allowed locally");
                Debug.Assert(Telnet.GetLocalState(TelnetOption.TerminalType), "NOT Option Enabled locally");
                const byte SEND = 1;
                const byte IS = 0;
                if (data.Length == 2) {
                    byte op = data[1];
                    if (op == SEND) {
                        const string type = "ANSI";
                        TraceWriteLine("Preparing to send TerminalType: {0}", type);
                        byte[] str = Encoding.ASCII.GetBytes(type);
                        Debug.Assert(str.Length != 0, "zero-length terminal type name string!");
                        Debug.Assert(str.Length == 0 || str[str.Length - 1] != 0, "last is null byte!");
                        byte[] rsp = new byte[2 + str.Length];
                        int idx = 0;
                        rsp[idx++] = optionNumber;
                        rsp[idx++] = IS;
                        str.CopyTo(rsp, idx);
                        idx += str.Length;
                        AddResponseSubNegotiation(rsp);
                        return;
                    } else {
                        TraceWriteLine("Ignoring TerminalType option; unrecognized code.");
                    }
                } else {
                    TraceWriteLine("Ignoring TerminalType option; is too short.");
                }
            }
            Debug.Assert(option != TelnetOption.NegotiateAboutWindowSize, "No NAWS subneg should be received!");
            TraceWriteLineIf(option == TelnetOption.NegotiateAboutWindowSize, "No NAWS subneg should be received!");
            //if (option == TelnetOption.NegotiateAboutWindowSize) {
            //    Debug.Assert(Telnet.GetLocalMode(TelnetOption.NegotiateAboutWindowSize), "NOT Option Allowed locally");
            //    Debug.Assert(Telnet.GetLocalState(TelnetOption.NegotiateAboutWindowSize), "NOT Option Enabled locally");
            //    // Nothing should be sent by the server!!
            //}
        }

        /// <exclude/>
        protected override void OnChangedState(bool local, TelnetOption option, bool newState, bool already, TelnetBaseStream.Command command)
        {
            if (local && option == TelnetOption.NegotiateAboutWindowSize) {
                if (newState && !already) {
                    SendNawsValue();
                }
            }
        }

        void SendNawsValue()
        {
            byte[] buf = new byte[5];
            buf[0] = (byte)TelnetOption.NegotiateAboutWindowSize;
            int width;
            int height;
            GetWindowSize(out width, out height);
            WriteAs16bitValue(width, buf, 1);
            WriteAs16bitValue(height, buf, 3);
            AddResponseSubNegotiation(buf);
        }

        /// <summary>
        /// When overridden in a derived class,
        /// returns the size of the window.
        /// </summary>
        /// -
        /// <remarks>Both width and height are sent as 16-bit values an error will
        /// occur if they don't fit in a <see cref="T:System.UInt16"/>.
        /// Zero can be returned to signal that the size is unknown.
        /// </remarks>
        /// -
        /// <param name="width">Returns an <see cref="T:System.Int32"/> containing
        /// the width of the window.
        /// </param>
        /// <param name="height">Returns an <see cref="T:System.Int32"/> containing
        /// the height of the window.
        /// </param>
        [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#")]
        [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#")]
        protected abstract void GetWindowSize(out int width, out int height);

        private static void WriteAs16bitValue(int value, byte[] buf, int offset)
        {
            ushort us = checked((ushort)value);
            short ss = unchecked((short)us);
            short be = System.Net.IPAddress.HostToNetworkOrder(ss);
            byte[] raw = BitConverter.GetBytes(be);
            raw.CopyTo(buf, offset);
        }

    }

}
